#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

using namespace std;
using namespace cv;

    Mat FrequencyFilter(Mat& src, int Sx, int Sy) {
    // src的宽高
    int R = src.rows;
    int C = src.cols;
    // 卷积核的宽高
    int r = 2 * Sx + 1;
    int c = 2 * Sy + 1;
    //将mask的深度作改变，提高精度
    Mat kernel = Mat::ones(r, c, CV_64FC1);
    /* 1.卷积边界扩充 */
    Mat src_padded;
    copyMakeBorder(src, src_padded, Sx, Sx, Sy, Sy, BORDER_REPLICATE);
    //将src.padded的深度作改变，提高精度 
    src_padded.convertTo(src_padded, CV_64FC1);
    /*2.补0以满足快速傅里叶变换的行数和列数 *///
    // 获取满足二维快速傅里叶变换的行数和列数，并且进行了避免缠绕错误的处理 
    int rows = getOptimalDFTSize(src_padded.rows + r - 1);
    int cols = getOptimalDFTSize(src_padded.cols + c - 1);
    //填充参数不一样，输出的图像边缘也会有很大的差别，在这里我们采用BORDER_REPLICATE，其会使输出的图像边界比直接补0更加平缓，大家可以改变边界填充方式，观察输出结果的边界形式
    Mat src_padded_zeros, kernel_zeros;
    copyMakeBorder(src_padded, src_padded_zeros, 0, rows - src_padded.rows, 0, cols - src_padded.cols, BORDER_REPLICATE);
    copyMakeBorder(kernel, kernel_zeros, 0, rows - r, 0, cols - c, BORDER_CONSTANT,Scalar(0, 0, 0));
    /* 3.快速傅里叶变换 */
    Mat fft_src, fft_kernel;
    //设定输出结果为复数
    dft(src_padded_zeros, fft_src, DFT_COMPLEX_OUTPUT);
    dft(kernel_zeros, fft_kernel, DFT_COMPLEX_OUTPUT);
    /* 4. 两个傅里叶变换点乘 */
    Mat Dot_Multiplication;
    mulSpectrums(fft_src, fft_kernel, Dot_Multiplication, DFT_ROWS);
    /* 5.1傅里叶逆变换，只取实部 *///
    //个人认为会存在一些误差，但在实际图像效果上来看与取幅值基本无差别，即人眼看不出什么差别
    Mat dst;
    dft(Dot_Multiplication, dst, DFT_INVERSE + DFT_SCALE + DFT_REAL_OUTPUT);
    normalize(dst, dst, 0, 255, NORM_MINMAX);
    Mat result = dst(Rect(Sy * 2, Sx * 2, C, R)).clone();
    result.convertTo(result, CV_8UC1);
    return result;
}

int main() {
    // 读取图片
    Mat img = imread("/home/lwj/opencv.21/cz.png", IMREAD_GRAYSCALE);
     imshow("原图",img);
    // 傅里叶变换卷积
    Mat fft_img = FrequencyFilter(img, 3, 3);
    imshow("傅里叶卷积 ", fft_img);
    waitKey(0);
    return 0;
}